package robocup.component;

import java.io.IOException;
import java.io.StringReader;
import java.util.Properties;

import robocup.component.actions.CoachAction;
import robocup.component.actions.InitAction;
import robocup.component.coach.CoachFactBase;
import robocup.component.coach.CoachWorld;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.CoachVisualInfo;
import robocup.component.infotypes.Info;
import robocup.component.infotypes.ServerParamInfo;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;
import robocup.parser9.CoachParser;
import robocup.parser9.ParseException;

/**
 * The trainer (sometimes also refered to as 'offline-coach') is an agent,
 * which is able to change the state of the game, such that certain situations
 * can be executed repetitive, e.g. for better testing, applying learning or
 * creating statistics. The TrainerAgent is not started directly, but
 * instantiated via the GUI or via commandline within <code>
 * soccerscope.model.SceneSetMaker</code>.
 * Within the robocup-package, the trainer-agent only serves as an interface to
 * the server. The evaluation and execution of training-scenarios is done in
 * <it>SoccerScope</it>.
 */
public class TrainerAgent
    extends AbstractAgent {

  /**
   * The trainers world model. Note that it also uses the same class as the
   * online-coach.
   */
  private CoachWorld  coachWorld;

  private CoachAction coachActionPlayer;

  private CoachAction coachActionBall;

  /**
   * Constructor of the trainer-agent simply forwards the properties of the
   * config-file.
   * 
   * @param props -
   *          the agent properties
   */
  public TrainerAgent(
      final Properties props) {

    super(props);
  }

  /**
   * No extra configuration parsed.
   */
  @Override
  protected void initConfig() {

  }

  /**
   * Creates new instances of the <code>coachWorld</code> and of the
   * <code>CoachParser</code>. Note, that the <code>coachWorld</code> is
   * not used in the moment.
   */
  @Override
  protected void initComponents() {

    this.coachWorld = new CoachWorld(new CoachFactBase());
    this.coachWorld.setParent(this);
    this.parser = new CoachParser(new StringReader(""), this);
    // coachTactic.start();
    this.coachActionPlayer = null;
    this.coachActionBall = null;
  }

  /**
   * Instantiates a connection as trainer to the server. Sets eye and ear on,
   * such that continuously messages are received. This is important, because
   * otherwise the trainers receive-connection might produce a timeout, which
   * causes the agent to terminate.
   */
  @Override
  protected boolean initProtocol() {

    try {
      // first connect
      final String init = new InitAction(AbstractAgent.TRAINER, this.teamName)
          .toString();
      this.connection.send(init);

      Info info = null;
      String rcv = null;

      rcv = this.connection.receive();
      if (rcv == null) {
        System.out.println("Error: Server does not respond in time!");
        return false;
      }

      // send eye and ear on:
      this.connection.send("(eye on)");
      this.connection.receive();
      this.connection.send("(ear on)");
      this.connection.receive();

      info = this.parser.parseCommand(rcv);

      System.out
          .println("[TrainerAgent] Trainer " + " successfully connected! ");

      // trigger updates
      this.informWorld(info);
      // informTactic(info);
    }
    catch (final IOException e) {
      e.printStackTrace();
      return false;
    }
    catch (final ParseException p) {
      p.printStackTrace();
      return false;
    }
    return true;
  }

  /**
   * Simply forwards the info to the coach-world.
   */
  @Override
  protected void processInfo(final Info info) {

    this.informWorld(info);
    // informTactic(info);
  }

  /**
   * This method delegates the updates of the worldmodel with given info, e.g.
   * the coachVI or the aural-info were sent to different update-methods of the
   * world-model.
   * 
   * @param info -
   *          a generic info
   */
  private void informWorld(final Info info) {

    if (info == null) {
      return;
    }
    if (info.getInfoType() == Info.TYPE_COACHVI) {
      this.coachWorld.update((CoachVisualInfo) info);
    }
    else if (info.getInfoType() == Info.TYPE_AURAL) {
      this.coachWorld.update((AuralInfo) info);
    }
    else if (info.getInfoType() == Info.TYPE_SPARAM) {
      this.coachWorld.setSConf(((ServerParamInfo) info).getSConf());
    }
  }

  /**
   * This method retrieves a CoachAction by: 1. retrieve the Player from the
   * CoachWorld 2. write his data into the field <code>coachAction</code> 3.
   * return the coachAction
   * 
   * @param team
   * @param i
   * @return
   */
  public CoachAction getCoachAction(final String teamName,
      final int unum) {

    Player[] team = null;
    if (teamName.equals(this.coachWorld.getTeamName(true))) {
      team = this.coachWorld.getTeammates();
    }
    else {
      team = this.coachWorld.getOpponents();
    }

    Player tmp = null;
    for (final Player p : team) {
      if (p.getNumber() == unum) {
        tmp = p;
        break;
      }
    }

    if (this.coachActionPlayer == null) {
      this.coachActionPlayer = new CoachAction(unum, teamName, tmp
          .getPosition().x, tmp.getPosition().y, tmp.getBodyDir(), tmp
          .getSpeed().x, tmp.getSpeed().y);
    }
    else {
      this.coachActionPlayer.setPosition(tmp.getPosition().x,
          tmp.getPosition().y);
      this.coachActionPlayer.setSpeed(tmp.getSpeed().x, tmp.getSpeed().y);
      this.coachActionPlayer.setUnum(unum);
      this.coachActionPlayer.setTeamName(teamName);
      this.coachActionPlayer.setBodyDir(tmp.getBodyDir());
    }
    return this.coachActionPlayer;
  }

  /**
   * This method retrieves a coachaction, which will fit directly to the
   * ball-position and speed.
   * 
   * @return - a coach-action for ball-movement
   */
  public CoachAction getCoachAction() {

    final Ball tmp = this.coachWorld.getBall();

    if (this.coachActionBall == null) {
      this.coachActionBall = new CoachAction(tmp.getPosition().x, tmp
          .getPosition().y, tmp.getSpeed().x, tmp.getSpeed().y);
    }
    else {
      this.coachActionBall
          .setPosition(tmp.getPosition().x, tmp.getPosition().y);
      this.coachActionBall.setSpeed(tmp.getSpeed().x, tmp.getSpeed().y);
    }
    return this.coachActionBall;
  }
}
